
#include <windows.h>
#include <winsock2.h>
#include <malloc.h>

#include "lib.h"
#include "patch.h"
#include "patchsrv.h"
#include "net.h"

// This header is included with the January 2001 and later Platform SDK.
// Uncomment the block below if compiling against an earlier version.
#include <mstcpip.h>
/* 
#define SIO_KEEPALIVE_VALS    _WSAIOW(IOC_VENDOR,4)
struct tcp_keepalive {
	u_long  onoff;
	u_long  keepalivetime;
	u_long  keepaliveinterval;
};
*/

enum Operations { READING, WRITING };

Net::Net(HANDLE client, NetCB* CB)
{
	handle = client;
	cb = CB;
	op = READING;
}

Net::~Net()
{
	close();
}

bool8
Net::initOk()
{
	return (bool8)(PortUser::initOk() && handle != (HANDLE)INVALID_SOCKET);
}

// luke 4/1/02: Attempt to enable TCP keepalives on the internal socket
bool8
Net::initKeepAlive()
{
	tcp_keepalive keepalive;
	DWORD dwDiscard = 0;
	int iEnable = 1;

	// Set desired TCP keepalive timeouts
	memset(&keepalive, 0, sizeof(tcp_keepalive));
	keepalive.onoff				= 1;					// Turn keepalives on
	keepalive.keepalivetime		= KEEPALIVE_TIMEOUT;	// Interval between keepalives
	keepalive.keepaliveinterval = KEEPALIVE_INTERVAL;	// Keepalive retry interval

	// Attempt to activate keepalives on the socket; try initializing with configured
	// timeouts before using the default values
	if (WSAIoctl((SOCKET)handle, SIO_KEEPALIVE_VALS, &keepalive, sizeof(tcp_keepalive), 
		NULL, 0, &dwDiscard, NULL, NULL) == 0)
		return true;

	if (setsockopt((SOCKET)handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&iEnable, sizeof(iEnable)) == 0)
		return true;

	// Unable to use TCP keepalives; fall back to app-level timeout
	return false;
}

void
Net::close()
{
	if (handle != (HANDLE)INVALID_SOCKET)
	{
		LINGER sockopt;
		int iDisable = 0;

		// luke 4/1/02: Disable keepalives before closing, just in case
		setsockopt((SOCKET)handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&iDisable, sizeof(iDisable));

		sockopt.l_onoff = 1;
		sockopt.l_linger = 0;		// "hard" disconnect
		setsockopt((SOCKET)handle, SOL_SOCKET,
			SO_LINGER, (char*)&sockopt, sizeof(LINGER));

		closesocket((SOCKET)handle);
		handle = (HANDLE)INVALID_SOCKET;
	}
}

bool8
Net::read(uint8* Buf, int32 Amt)
{
	WSABUF wsabuf;
	DWORD nRecv, flags = 0, err;

	if ((SOCKET)handle == (SOCKET)INVALID_SOCKET)
		return false;

	ASSERT(1 == HasOverlappedIoCompleted(over));

    // Clear buffer before reading
    memset( Buf, 0, Amt );

	op = READING;
	wsabuf.len = (uint32)Amt;
	wsabuf.buf = (char*)Buf;

	if (0 == WSARecv((SOCKET)handle, &wsabuf, 1, &nRecv, &flags,
		(LPWSAOVERLAPPED)over, (LPWSAOVERLAPPED_COMPLETION_ROUTINE)0))
	{
		ASSERT((int32)nRecv <= Amt);
		return true;
	}

	err = WSAGetLastError();

	if (err == WSA_IO_PENDING)
		return true;

	over->Internal = 0;

	if (err == WSAECONNRESET)
		return false;

	ASSERT(err == 0);

	// at this point it's safe to call CB since no other i/o is
	// posted and can be processed.  (This is only true if max of ONE
	// i/o req is ever allowed at any one time for a single File obj)
	// NOTE:  if >1 i/o req is desired, then must synchronize ALL calls to CB
	//
	if (cb)
		cb->netErr(err);

	return false;
}


bool8
Net::isDatatoRead(int32 Amt, int32 index, uint8 val)
{
	WSABUF wsabuf;
	char* buf = (char*)_alloca(Amt);
	DWORD nRecv, flags = MSG_PEEK;

	wsabuf.len = (uint32)Amt;
	wsabuf.buf = (char*)buf;

	if (0 == WSARecv((SOCKET)handle, &wsabuf, 1, &nRecv, &flags,
		(LPWSAOVERLAPPED)0, (LPWSAOVERLAPPED_COMPLETION_ROUTINE)0))
	{
		ASSERT((int32)nRecv <= Amt);
		
		if (buf[index] == val)
			return true;
		return false;
	}
	return false;
}



bool8
Net::write(uint8* Buf, int32 Amt)
{
	WSABUF wsabuf;
	DWORD nSent, err;

	if ((SOCKET)handle == (SOCKET)INVALID_SOCKET)
		return false;

	op = WRITING;
	wsabuf.len = (uint32)Amt;
	wsabuf.buf = (char*)Buf;

	if (0 == WSASend((SOCKET)handle, &wsabuf, 1, &nSent, 0,
		(LPWSAOVERLAPPED)over, (LPWSAOVERLAPPED_COMPLETION_ROUTINE)0))
		return true;

	err = WSAGetLastError();

	if (err == WSA_IO_PENDING)
		return true;

	over->Internal = 0;

	if (err == WSAECONNRESET || err == WSAECONNABORTED)
		return false;

	ASSERT(err == 0);

	// at this point it's safe to call CB since no other i/o is
	// posted and can be processed.  (This is only true if max of ONE
	// i/o req is ever allowed at any one time for a single File obj)
	// NOTE:  if >1 i/o req is desired, then must synchronize ALL calls to CB
	//
	if (cb)
		cb->netErr(err);

	return false;
}

void
Net::ioDone(DWORD Amt)
{
	if (cb)
		if (op == READING)
		{
			cb->netIn(Amt);

			PatchServer::This->addBytesIn(Amt);
		}
		else
		{
			cb->netOut(Amt);

			PatchServer::This->addBytesOut(Amt);
		}
}

void
Net::ioErr(DWORD Err)
{
	if (cb)
		cb->netErr(Err);
}

void
Net::ioTimer(DWORD Amt)
{
	cb->netTimer(Amt);
}
